/*
 * DigestUtil.java
 * This file is part of Portecle, a multipurpose keystore and certificate tool.
 *
 * Copyright © 2004 Wayne Grant, waynedgrant@hotmail.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package com.googlecode.bluetools.common.security;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ResourceBundle;

/**
 * Provides utility methods for the creation of message digests.
 */
public final class DigestUtil {
	/** Resource bundle */
	private static ResourceBundle m_res = ResourceBundle.getBundle("com/googlecode/bluetools/common/security/resources");

	/**
	 * Used building output as Hex
	 */
	private static final char[] DIGITS = {
			'0',
			'1',
			'2',
			'3',
			'4',
			'5',
			'6',
			'7',
			'8',
			'9',
			'a',
			'b',
			'c',
			'd',
			'e',
			'f'
	};

	/**
	 * Private to prevent construction.
	 */
	private DigestUtil() {
	}

	/**
	 * Get the digest of a message as a formatted String.
	 * 
	 * @param bMessage The message to digest
	 * @param digestType The message digest algorithm
	 * @return The message digest
	 * @throws CryptoException If there was a problem generating the message digest
	 */
	public static String getMessageDigest(byte[] bMessage, String digestType) throws CryptoException {
		// Create message digest object using the supplied algorithm
		MessageDigest messageDigest;
		try {
			messageDigest = MessageDigest.getInstance(digestType);
		}
		catch (NoSuchAlgorithmException ex) {
			throw new CryptoException(m_res.getString("NoCreateDigest.exception.message"), ex);
		}

		// Create raw message digest
		byte[] bFingerPrint = messageDigest.digest(bMessage);

		// Place the raw message digest into a StringBuffer as a Hex number
		StringBuffer strBuff = new StringBuffer(new BigInteger(1, bFingerPrint).toString(16).toUpperCase());

		// Odd number of characters so add in a padding "0"
		if ((strBuff.length() % 2) != 0) {
			strBuff.insert(0, '0');
		}

		// Place colons at every two hex characters
		if (strBuff.length() > 2) {
			for (int iCnt = 2; iCnt < strBuff.length(); iCnt += 3) {
				strBuff.insert(iCnt, ':');
			}
		}

		// Return the formatted message digest
		return strBuff.toString();
	}

	/**
	 * Converts an array of characters representing hexidecimal values into an array of bytes of those same values. The
	 * returned array will be half the length of the passed array, as it takes two characters to represent any given
	 * byte. An exception is thrown if the passed char array has an odd number of elements.
	 * 
	 * @param data An array of characters containing hexidecimal digits
	 * @return A byte array containing binary data decoded from the supplied char array.
	 * @throws DecoderException Thrown if an odd number or illegal of characters is supplied
	 */
	public static byte[] decodeHex(char[] data) throws CryptoException {

		int len = data.length;

		if ((len & 0x01) != 0) {
			throw new CryptoException("Odd number of characters.");
		}

		byte[] out = new byte[len >> 1];

		// two characters form the hex value.
		for (int i = 0, j = 0; j < len; i++) {
			int f = toDigit(data[j], j) << 4;
			j++;
			f = f | toDigit(data[j], j);
			j++;
			out[i] = (byte) (f & 0xFF);
		}

		return out;
	}

	/**
	 * Converts a hexadecimal character to an integer.
	 * 
	 * @param ch A character to convert to an integer digit
	 * @param index The index of the character in the source
	 * @return An integer
	 * @throws DecoderException Thrown if ch is an illegal hex character
	 */
	protected static int toDigit(char ch, int index) throws CryptoException {
		int digit = Character.digit(ch, 16);
		if (digit == -1) {
			throw new CryptoException("Illegal hexadecimal charcter " + ch + " at index " + index);
		}
		return digit;
	}

	/**
	 * Converts an array of bytes into an array of characters representing the hexidecimal values of each byte in order.
	 * The returned array will be double the length of the passed array, as it takes two characters to represent any
	 * given byte.
	 * 
	 * @param data a byte[] to convert to Hex characters
	 * @return A char[] containing hexidecimal characters
	 */
	public static char[] encodeHex(byte[] data) {

		int l = data.length;

		char[] out = new char[l << 1];

		// two characters form the hex value.
		for (int i = 0, j = 0; i < l; i++) {
			out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
			out[j++] = DIGITS[0x0F & data[i]];
		}

		return out;
	}

	/**
	 * Returns a MessageDigest for the given <code>algorithm</code>.
	 * 
	 * @param algorithm The MessageDigest algorithm name.
	 * @return An MD5 digest instance.
	 * @throws RuntimeException when a {@link java.security.NoSuchAlgorithmException} is caught,
	 */
	static MessageDigest getDigest(String algorithm) {
		try {
			return MessageDigest.getInstance(algorithm);
		}
		catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	/**
	 * Returns an MD5 MessageDigest.
	 * 
	 * @return An MD5 digest instance.
	 * @throws RuntimeException when a {@link java.security.NoSuchAlgorithmException} is caught,
	 */
	static MessageDigest getMd5Digest() {
		return getDigest("MD5");
	}

	/**
	 * Returns an SHA digest.
	 * 
	 * @return An SHA digest instance.
	 * @throws RuntimeException when a {@link java.security.NoSuchAlgorithmException} is caught,
	 */
	static MessageDigest getShaDigest() {
		return getDigest("SHA");
	}

	public static byte[] getFileMD5(File file) throws IOException {
		FileInputStream in = new FileInputStream(file);
		FileChannel ch = in.getChannel();
		MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
		MessageDigest messageDigest = getMd5Digest();
		messageDigest.update(byteBuffer);
		return messageDigest.digest();
	}

	public static String getFileMD5Hex(File file) throws IOException {
		return new String(encodeHex(getFileMD5(file)));
	}
}
